package org.springframework.security.provisioning;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.log.LogMessage;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsPasswordService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.core.userdetails.memory.UserAttribute;
import org.springframework.security.core.userdetails.memory.UserAttributeEditor;
import org.springframework.util.Assert;

import java.util.*;

/**
 * @author Dillon
 * @date 2024/7/1
 * @slogan 致敬大师 致敬未来的你
 * @desc 基于内存管理登录用户实现类
 */
public class InMemoryUserDetailsManager implements UserDetailsManager, UserDetailsPasswordService {

	/**
	 * 日志对象
	 */
	protected final Log logger = LogFactory.getLog(getClass());

	/**
	 * 登录用户缓存MAP
	 */
	private final Map<String, MutableUserDetails> users = new HashMap<>();

	/**
	 * 认证用户策略类
	 */
	private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder.getContextHolderStrategy();

	/**
	 * 认证管理器
	 */
	private AuthenticationManager authenticationManager;

	/**
	 * 构造函数
	 */
	public InMemoryUserDetailsManager() {
	}

	/**
	 * 构造函数
	 *
	 * @param users 待初始化自定义用户列表
	 */
	public InMemoryUserDetailsManager(Collection<UserDetails> users) {
		for (UserDetails user : users) {
			createUser(user);
		}
	}

	/**
	 * 构造函数
	 *
	 * @param users 待初始化自定义用户列表
	 */
	public InMemoryUserDetailsManager(UserDetails... users) {
		for (UserDetails user : users) {
			createUser(user);
		}
	}

	/**
	 * 构造函数
	 *
	 * @param users 用户属性对象
	 */
	public InMemoryUserDetailsManager(Properties users) {
		Enumeration<?> names = users.propertyNames();
		UserAttributeEditor editor = new UserAttributeEditor();
		while (names.hasMoreElements()) {
			String name = (String) names.nextElement();
			editor.setAsText(users.getProperty(name));
			UserAttribute attr = (UserAttribute) editor.getValue();
			Assert.notNull(attr, () -> "用户名对象 '" + name + "' 不能和自定义用户同名");
			createUser(createUserDetails(name, attr));
		}
	}

	/**
	 * 创建自定义用户对象
	 *
	 * @param name 用户名
	 * @param attr 权限列表
	 * @return 自定义用户对象
	 */
	private User createUserDetails(String name, UserAttribute attr) {
		return new User(name, attr.getPassword(), attr.isEnabled(), true, true, true, attr.getAuthorities());
	}

	/**
	 * 创建一个自定义用户
	 *
	 * @param user 自定义用户
	 */
	@Override
	public void createUser(UserDetails user) {
		Assert.isTrue(!userExists(user.getUsername()), "自定义用户不能为空");
		this.users.put(user.getUsername().toLowerCase(), new MutableUser(user));
	}

	/**
	 * 移除自定义用户
	 *
	 * @param username 自定义用户名
	 */
	@Override
	public void deleteUser(String username) {
		this.users.remove(username.toLowerCase());
	}

	/**
	 * 更新自定义用户信息
	 *
	 * @param user 自定义用户
	 */
	@Override
	public void updateUser(UserDetails user) {
		Assert.isTrue(userExists(user.getUsername()), "用户不能为空");
		this.users.put(user.getUsername().toLowerCase(), new MutableUser(user));
	}

	/**
	 * 判断当前系统中是否存在该自定义用户
	 *
	 * @param username 用户名
	 * @return 是否存在结果
	 */
	@Override
	public boolean userExists(String username) {
		return this.users.containsKey(username.toLowerCase());
	}

	/**
	 * 更新自定义用户密码
	 * 在获取到自定义用户后，才能调用该方法
	 *
	 * @param oldPassword 旧密码
	 * @param newPassword 新密码
	 */
	@Override
	public void changePassword(String oldPassword, String newPassword) {
		// 从系统中获取当前用户
		Authentication currentUser = this.securityContextHolderStrategy.getContext().getAuthentication();
		if (currentUser == null) {
			// 跳转到登录界面
			throw new AccessDeniedException("当前用户未登录，不能修改密码");
		}
		String username = currentUser.getName();
		this.logger.debug(LogMessage.format("更改用户 '%s' 的密码", username));
		// 通过认证管理器更新用户密码
		if (this.authenticationManager != null) {
			this.logger.debug(LogMessage.format("Reauthenticating user '%s' for password change request.", username));
			this.authenticationManager.authenticate(UsernamePasswordAuthenticationToken.unauthenticated(username, oldPassword));
		} else {
			this.logger.debug("未设置认证管理器，不能更改密码.");
		}
		MutableUserDetails user = this.users.get(username);
		Assert.state(user != null, "数据库中不存在当前用户.");
		user.setPassword(newPassword);
	}

	/**
	 * 更新用户的密码信息
	 *
	 * @param user        当前登录用户
	 * @param newPassword 新的密码
	 * @return 更新后的自定义用户
	 */
	@Override
	public UserDetails updatePassword(UserDetails user, String newPassword) {
		String username = user.getUsername();
		MutableUserDetails mutableUser = this.users.get(username.toLowerCase());
		mutableUser.setPassword(newPassword);
		return mutableUser;
	}

	/**
	 * 通过用户名获取自定义用户
	 *
	 * @param username 用户名
	 * @return 自定义用户
	 * @throws UsernameNotFoundException 用户未找到异常
	 */
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		UserDetails user = this.users.get(username.toLowerCase());
		if (user == null) {
			throw new UsernameNotFoundException(username);
		}
		return new User(user.getUsername(), user.getPassword(), user.isEnabled(), user.isAccountNonExpired(),
				user.isCredentialsNonExpired(), user.isAccountNonLocked(), user.getAuthorities());
	}

	/**
	 * 设置用户认证策略缓存类
	 *
	 * @param securityContextHolderStrategy 认证策略缓存类
	 */
	public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
		Assert.notNull(securityContextHolderStrategy, "策略缓存类不能为空");
		this.securityContextHolderStrategy = securityContextHolderStrategy;
	}

	/**
	 * 设置认证管理器
	 *
	 * @param authenticationManager 热敏纸管理器
	 */
	public void setAuthenticationManager(AuthenticationManager authenticationManager) {
		this.authenticationManager = authenticationManager;
	}

}
